﻿// 
//    Copyright 2010 The JaernCloud Project
// 
//    Licensed under the Apache License, Version 2.0 (the "License");
//    you may not use this file except in compliance with the License.
//    You may obtain a copy of the License at
// 
//        http://www.apache.org/licenses/LICENSE-2.0
// 
//    Unless required by applicable law or agreed to in writing, software
//    distributed under the License is distributed on an "AS IS" BASIS,
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//    See the License for the specific language governing permissions and
//    limitations under the License.
// 
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
using System.Linq;
using System.Text;
using JaernCloud.Language.Compiler;
using JaernCloud.Language.Compiler.Ast;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Tagging;
using Microsoft.VisualStudio.Utilities;
using Microsoft.Scripting;

namespace JaernCloud.VisualStudio {
    [Export(typeof(ITaggerProvider))]
    [ContentType("JaernCloud")]
    [TagType(typeof(JaernCloudTokenTag))]
    internal sealed class JaernCloudTokenTagProvider : ITaggerProvider {
        public ITagger<T> CreateTagger<T>(ITextBuffer buffer) where T : ITag {
            return new JaernCloudTokenTagger(buffer) as ITagger<T>;
        }
    }

    public class JaernCloudTokenTag : ITag {
        public AstNodeKind Type { get; private set; }

        public JaernCloudTokenTag(AstNodeKind type) {
            Type = type;
        }
    }

    internal sealed class JaernCloudTokenTagger : ITagger<JaernCloudTokenTag> {
        ITextBuffer buffer;
        // CONSIDER: Is it better to map strings to classifier type?
        IDictionary<AstNodeKind, HashSet<string>> jcTypes;
        Parser jcParser;
        Scanner jcScanner;

        // TODO: Handle AstOperator, AstConstant, AstConstantValueKind
        // TODO: Handle reserved words

        public JaernCloudTokenTagger(ITextBuffer buffer) {
            this.buffer = buffer;

            jcTypes = new Dictionary<AstNodeKind, HashSet<string>>();
            foreach (AstNodeKind astNodeKind in Enum.GetValues(typeof(AstNodeKind))) {
                jcTypes[astNodeKind] = new HashSet<string>();
            }

            using (MemoryStream ms = new MemoryStream(Encoding.ASCII.GetBytes(buffer.CurrentSnapshot.GetText()))) {
                jcScanner = new Scanner(ms);
                jcParser = new Parser(jcScanner);
                jcParser.Parse();
            }
        }

        public event EventHandler<SnapshotSpanEventArgs> TagsChanged {
            add { }
            remove { }
        }

        public IEnumerable<ITagSpan<JaernCloudTokenTag>> GetTags(NormalizedSnapshotSpanCollection spans) {
            foreach (SnapshotSpan curSpan in spans) {
                if (curSpan.IsEmpty || String.IsNullOrWhiteSpace(curSpan.GetText()))
                    continue;

                int startLoc = curSpan.Span.Start;
                int endLoc = curSpan.Span.End;
                int curLoc = startLoc;

                string txt = curSpan.GetText();
                HashSet<AstTag> tokens = GetTokens(startLoc, endLoc);

                foreach (AstTag jcTag in tokens) {
                    // TODO: Add inherits from entries
                    HashSet<string> stringTokens = new HashSet<string>();
                    if (!String.IsNullOrEmpty(jcTag.Id))
                        stringTokens.Add(jcTag.Id);
                    stringTokens.Add(jcTag.Name);

                    bool first = true;
                    foreach (string t in stringTokens) {
                        if (first) {
                            first = false;

                            AstNodeKind astNodeKind = (AstNodeKind)Enum.Parse(typeof(AstNodeKind), jcTag.TokenInfo.Category.ToString());
                            if (jcTypes[astNodeKind].Contains(t)) {
                                var tokenSpan = new SnapshotSpan(curSpan.Snapshot, new Span(startLoc + txt.IndexOf(t), t.Length));
                                if (tokenSpan.IntersectsWith(curSpan))
                                    yield return new TagSpan<JaernCloudTokenTag>(tokenSpan, new JaernCloudTokenTag(astNodeKind));
                            }
                        } else {
                            AstNodeKind astNodeKind = AstNodeKind.Identifier;
                            try {
                                astNodeKind = (AstNodeKind)Enum.Parse(typeof(AstNodeKind), t, true);
                            } catch (Exception ex) {
                                // Exception can be because we are dealing with an html, css or something type node
                                // TODO: Fix
                            }
                            if (astNodeKind == AstNodeKind.Identifier)
                                continue;

                            if (jcTypes[astNodeKind].Contains(t)) {
                                var tokenSpan = new SnapshotSpan(curSpan.Snapshot, new Span(startLoc + txt.IndexOf(t), t.Length));
                                if (tokenSpan.IntersectsWith(curSpan))
                                    yield return new TagSpan<JaernCloudTokenTag>(tokenSpan, new JaernCloudTokenTag(astNodeKind));
                            }
                        }
                    }
                }
            }
        }

        private HashSet<AstTag> GetTokens(int start, int end) {
            HashSet<AstTag> tokens = new HashSet<AstTag>();

            AstNode node = jcParser.Root;
            GetTokens(node, start, end, ref tokens);

            return tokens;
        }

        private void GetTokens(AstNode node, int start, int end, ref HashSet<AstTag> tokens) {
            // TODO: Handle inherits-from entries
            if (node.Tag != null) {
                TokenInfo ti = node.Tag.TokenInfo;

                if (ti.SourceSpan.Start.Index >= start && ti.SourceSpan.Start.Index <= end) {
                    ((HashSet<AstTag>)tokens).Add(node.Tag);
                    bool inheritsFrom = false;
                    AstNodeKind astNodeKind = (AstNodeKind)Enum.Parse(typeof(AstNodeKind), ti.Category.ToString());
                    if (node.Tag.Id != null) {
                        if (!jcTypes[astNodeKind].Contains(node.Tag.Id))
                            jcTypes[astNodeKind].Add(node.Tag.Id);
                    } else if(node.InheritsFrom != null && node.InheritsFrom.Count > 0) {
                        inheritsFrom = true;
                        if (!jcTypes[astNodeKind].Contains(node.Tag.Name))
                            jcTypes[astNodeKind].Add(node.Tag.Name);
                    }

                    // Try to parse
                    try {
                        if (inheritsFrom) {
                            string name = node.InheritsFrom[0].TagSelector.Tags[0].Name;
                            astNodeKind = (AstNodeKind)Enum.Parse(typeof(AstNodeKind), name, true);
                            if (!jcTypes[astNodeKind].Contains(name))
                                jcTypes[astNodeKind].Add(name);
                        } else {
                            astNodeKind = (AstNodeKind)Enum.Parse(typeof(AstNodeKind), node.Tag.Name, true);
                            if (!jcTypes[astNodeKind].Contains(node.Tag.Name))
                                jcTypes[astNodeKind].Add(node.Tag.Name);
                        }
                    } catch (Exception ex) {
                        // Exception can be because we are dealing with an html, css or something type node
                        // TODO: Fix
                    }
                }
            }

            foreach (AstNode childNode in node.Children) {
                if (childNode.Tag != null) {
                    TokenInfo cti = childNode.Tag.TokenInfo;
                    if (cti.SourceSpan.End.Index < start || cti.SourceSpan.Start.Index > end)
                        continue;

                    GetTokens(childNode, start, end, ref tokens);
                }
            }
        }
    }
}
